package vsm.vsm_server.service.impl;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import vsm.vsm_server.exception.BizErrorCodeEnum;
import vsm.vsm_server.exception.BizException;
import vsm.vsm_server.mapper.RecMapper;
import vsm.vsm_server.pojo.CF.User_User_CF;
import vsm.vsm_server.pojo.CF.User_Video_CF;
import vsm.vsm_server.pojo.CF.Video_Video_CF;
import vsm.vsm_server.pojo.User.User;
import vsm.vsm_server.pojo.Video.Video;
import vsm.vsm_server.pojo.Video.VideoScore;
import vsm.vsm_server.pojo.Video.VideoTag;
import vsm.vsm_server.service.*;

import java.math.BigInteger;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class RecServiceImpl implements RecService {
    private RecMapper recMapper;


    public RecServiceImpl(RecMapper recMapper){
        this.recMapper = recMapper;
    }
    @Autowired
    VideoService videoService;
    @Autowired
    CFService cfService;
    @Autowired
    HistoryService historyService;
    @Autowired
    UserService userService;
    @Autowired
    PredictService predictService;
    @Autowired
    RelationService relationService;
    @Autowired
    private StringRedisTemplate redisTemplate; // 注入 Redis 模板
    @Override
    public List<BigInteger> getRandomVideoList(BigInteger userid, Integer videocount) {


        List<BigInteger> videoList = new ArrayList<>();
        videoList = this.ReorderVideoList( videoList,userid);

        //三级流量池
        List<BigInteger> thirdVideoList = recMapper.getRandomVideoList(videocount);

        return thirdVideoList;
    }
/*
    public List<BigInteger> GetRedisVideoList(BigInteger userid,int count) throws Exception { //用户调用redis中的videolist
        List<BigInteger> videoList = new ArrayList<>();
        String redisKey = "video_userid:" + userid;
        boolean exists = redisTemplate.hasKey(redisKey);
        if (exists) { //若存在
            String up = redisTemplate.opsForValue().get(redisKey);
            List<BigInteger> list = JSON.parseObject(up, List.class);
            if(list.size()<count){ //若所需列表小于存储列表长度
                PutRedisVideoList(userid);
                throw new BizException(BizErrorCodeEnum.REDIS_LIST_NOT_ENOUGH);
            }else{
                videoList = list.subList(0,count-1);
                list = list.subList(count,list.size());
                String s = JSON.toJSONString(list);
                redisTemplate.opsForValue().set(redisKey, s, 5, TimeUnit.HOURS); //5min 过期
            }
        }else{
            PutRedisVideoList(userid);
            throw  new BizException(BizErrorCodeEnum.REDIS_LIST_IS_NULL);
        }
        return videoList;

    }*/

    // --- Redis 相关方法 (GetRedisVideoList, PutRedisVideoList) ---
    @Override
    public List<BigInteger> GetRedisVideoList(BigInteger userid, int count) throws Exception { //用户调用redis中的videolist
        List<BigInteger> videoList = new ArrayList<>();
        String redisKey = "video_userid:" + userid;
        String rawList = redisTemplate.opsForValue().get(redisKey);

        if (rawList != null && !rawList.isEmpty()) { // 检查非空
            List<BigInteger> list = JSON.parseArray(rawList, BigInteger.class); // 解析为 BigInteger 列表

            if (list.size() >= count) { // 检查长度 >= count
                videoList = new ArrayList<>(list.subList(0, count)); // 获取前 count 个，创建新列表避免修改原列表视图
                List<BigInteger> remainingList = new ArrayList<>(list.subList(count, list.size())); // 获取剩余的
                String s = JSON.toJSONString(remainingList);
                // 设置 TTL 时更新，避免列表空了还占着 key
                if (!remainingList.isEmpty()) {
                    redisTemplate.opsForValue().set(redisKey, s, 5, TimeUnit.MINUTES); // 5min 过期
                } else {
                    redisTemplate.delete(redisKey); // 如果取完了，直接删除 key
                }
                System.out.println("User " + userid + ": Got " + count + " videos from Redis cache. Remaining: " + remainingList.size());
            } else {
                System.out.println("User " + userid + ": Redis cache size (" + list.size() + ") not enough for request (" + count + "). Triggering refill.");
                // 缓存不足：返回剩余的，并异步触发补充 (更优) 或同步触发 (简单)
                videoList = new ArrayList<>(list); // 返回所有剩余的
                redisTemplate.delete(redisKey); // 清空旧缓存
                // 触发补充 (同步或异步)
                PutRedisVideoList(userid); // 同步补充
                // Or: executorService.submit(() -> PutRedisVideoList(userid)); // 异步补充

                // 不直接抛出异常，而是返回了剩余内容，让客户端尽可能有内容展示
                // 如果希望严格要求数量，可以取消上面的返回，取消删除 key，然后抛出异常
                // throw new BizException(BizErrorCodeEnum.REDIS_LIST_NOT_ENOUGH);
            }
        } else {
            System.out.println("User " + userid + ": Redis cache miss or empty. Triggering calculation.");
            PutRedisVideoList(userid); // 缓存不存在，直接计算
            // 第一次计算后，理论上应该有数据了，可以让用户稍后重试或再次调用 GetRedisVideoList
            // 为避免死循环，这里可以选择再次尝试获取一次，或者直接抛异常/返回空列表
            // 再次尝试获取:
            String newlyFilledRawList = redisTemplate.opsForValue().get(redisKey);
            if (newlyFilledRawList != null && !newlyFilledRawList.isEmpty()) {
                List<BigInteger> newlyFilledList = JSON.parseArray(newlyFilledRawList, BigInteger.class);
                if(newlyFilledList.size() >= count) {
                    // ... (重复上面获取成功的逻辑) ...
                    videoList = new ArrayList<>(newlyFilledList.subList(0, count));
                    List<BigInteger> remainingList = new ArrayList<>(newlyFilledList.subList(count, newlyFilledList.size()));
                    String s = JSON.toJSONString(remainingList);
                    if (!remainingList.isEmpty()) {
                        redisTemplate.opsForValue().set(redisKey, s, 5, TimeUnit.HOURS);
                    } else {
                        redisTemplate.delete(redisKey);
                    }
                    System.out.println("User " + userid + ": Got " + count + " videos after first calculation.");
                    return videoList; // 返回成功获取的列表
                } else {
                    // 计算后仍然不足 (可能召回/排序结果很少)
                    videoList = new ArrayList<>(newlyFilledList); // 返回所有能给的
                    redisTemplate.delete(redisKey);
                    System.out.println("User " + userid + ": Got " + videoList.size() + " videos after calculation (less than requested).");
                    return videoList;
                }
            } else {
                // 计算后仍然是空的 (极端情况)
                System.err.println("User " + userid + ": Failed to generate recommendation list.");
                throw new BizException(BizErrorCodeEnum.REDIS_LIST_IS_NULL); // 或者返回空列表
            }
            // 原始逻辑: throw new BizException(BizErrorCodeEnum.REDIS_LIST_IS_NULL);
        }
        return videoList;
    }

    public void PutRedisVideoList(BigInteger userid){ //当用户对当前内容观看率下降或刷新时或redis里的缓存不足时调用
        System.out.println("User " + userid + ": Starting PutRedisVideoList process...");
        // 1. 召回 (包括过滤)
        List<BigInteger> recalledVideoIds = CFVideoList(userid);
        if (recalledVideoIds.isEmpty()) {
            System.out.println("User " + userid + ": No videos found after recall and filtering. Skipping ranking.");
            // Consider adding fallback logic here, e.g., recommend global hot videos
            return;
        }

        // 2. 初排
        List<BigInteger> coarseRankedVideoIds = SimpleOrderVideoList(recalledVideoIds, userid);
        if (coarseRankedVideoIds.isEmpty()) {
            System.out.println("User " + userid + ": No videos found after coarse ranking. Skipping fine ranking.");
            return;
        }

        // 3. 精排
        List<BigInteger> fineRankedVideoIds = ComplexOrderVideoList(coarseRankedVideoIds, userid);
        if (fineRankedVideoIds.isEmpty()) {
            // If fine ranking fails or returns empty, maybe fallback to coarse ranked list?
            System.out.println("User " + userid + ": No videos found after fine ranking. Using coarse ranked list as fallback.");
            fineRankedVideoIds = coarseRankedVideoIds;
            // Or handle based on requirements
            if (fineRankedVideoIds.isEmpty()) return; // Still empty, give up
        }


        // 4. 重排
        List<BigInteger> reorderedVideoList = ReorderVideoList(fineRankedVideoIds, userid);
        if (reorderedVideoList.isEmpty()){
            System.out.println("User " + userid + ": No videos found after re-ranking. Using fine ranked list as fallback.");
            reorderedVideoList = fineRankedVideoIds; // Fallback to fine ranked list
            if (reorderedVideoList.isEmpty()) return; // Still empty
        }


        // 5. 放入redis (使用去重后的最终列表)
        String redisKey = "video_userid:" + userid;
        // 注意：这里的逻辑应该是直接覆盖旧的列表，而不是追加，因为这是一个全新的推荐结果集
        if (!reorderedVideoList.isEmpty()) {
            // 去重 (理论上重排后不应有重复，但保险起见)
            List<BigInteger> finalList = orderList(reorderedVideoList);

            String s = JSON.toJSONString(finalList);
            redisTemplate.opsForValue().set(redisKey, s, 5, TimeUnit.MINUTES); // 5min 过期，TTL 可配置
            System.out.println("User " + userid + ": Successfully stored " + finalList.size() + " recommended videos in Redis.");
        } else {
            System.out.println("User " + userid + ": Final recommended list is empty. Nothing stored in Redis.");
            // Optionally delete the key if it exists
            redisTemplate.delete(redisKey);
        }
    }
/*    public void PutRedisVideoList(BigInteger userid){ //当用户对当前内容观看率下降或刷新时或redis里的缓存不足时调用
        //召回
        List<BigInteger> videoList = CFVideoList(userid);
        //初排
        videoList = SimpleOrderVideoList(videoList,userid);
        //精排
        videoList = ComplexOrderVideoList(videoList,userid);
        //重排
        videoList = ReorderVideoList(videoList,userid);
        //四、放入redis
        //1.放入redis
        String redisKey = "video_userid:" + userid;
        //判断存不存在该值，若存在则增加值
        boolean exists = redisTemplate.hasKey(redisKey);
        if (exists) {
            String up = redisTemplate.opsForValue().get(redisKey);
            List<BigInteger> v = JSON.parseObject(up, List.class);
            v.addAll(videoList);
            videoList = v;
        }
        //2.去重
        videoList = orderList(videoList);
        //3.放入redis
        String s = JSON.toJSONString(videoList);
        redisTemplate.opsForValue().set(redisKey, s, 5, TimeUnit.HOURS); //5min 过期
    }*/
    private List<BigInteger> orderList(List<BigInteger> list){ //去重
        Set<BigInteger> set = new LinkedHashSet<>(list);
        List<BigInteger> newlist = new ArrayList<>(set);
        return newlist;
    }

    public List<BigInteger> ItemCF(BigInteger userid){ //物品协同过滤 https://blog.csdn.net/weixin_46838716/article/details/126136865?spm=1001.2014.3001.5502
        List<BigInteger> videoList = new ArrayList<>();
        Map<BigInteger,Double> scoreMap = new HashMap<>();
        //1.获取用户感兴趣的视频
        List<User_Video_CF> userVideoCfList = cfService.getUserInterest(userid,200);
        //2.获取与某视频相似的视频
        for(User_Video_CF uv :userVideoCfList){
            List<Video_Video_CF> videoVideoCfList = cfService.getItemSim(uv.getToVideoId(),20);
            log.info("videoVideo:{}",videoVideoCfList );

            for(Video_Video_CF vv:videoVideoCfList){
                // 计算得分：用户兴趣 × 视频相似度
                double score = uv.getInterest() * vv.getInterest();
                // 使用 merge 累加相同视频的得分（避免覆盖）
                scoreMap.merge(vv.getToVideoId(), score, Double::sum);
            }
        }
        log.info("itemCF:{}",scoreMap);
        // 3. 按得分从高到低排序，并提取视频ID到列表
        videoList = this.videoSort(100,scoreMap);

        return videoList;
    }
    private List<BigInteger> UserCF(BigInteger userid){ //用户协同过滤 https://blog.csdn.net/weixin_46838716/article/details/126139160?spm=1001.2014.3001.5502
        List<BigInteger> videoList = new ArrayList<>();
        Map<BigInteger,Double> scoreMap = new HashMap<>();
        //1.获取相似用户
        List<User_User_CF> userUserCfList = cfService.getUserSim(userid,200);
        //2.获取每个用户感兴趣的视频
        for(User_User_CF uu:userUserCfList){
            List<User_Video_CF> userVideoCfList = cfService.getUserInterest(uu.getToUserId(),20);
            for(User_Video_CF uv:userVideoCfList){
                // 计算得分：用户兴趣 × 视频相似度
                double score = uv.getInterest() * uu.getInterest();
                // 使用 merge 累加相同视频的得分（避免覆盖）
                scoreMap.merge(uv.getToVideoId(), score, Double::sum);
            }
        }
        // 3. 按得分从高到低排序，并提取视频ID到列表
        videoList = videoSort(100,scoreMap);

        return videoList;
    }
    private List<BigInteger> videoSort(int topN,Map<BigInteger,Double> scoreMap){
        return scoreMap.entrySet().stream()
                .sorted(Map.Entry.<BigInteger, Double>comparingByValue().reversed())
                .map(Map.Entry::getKey)
                .limit(topN)
                .collect(Collectors.toList());
    }
    private List<BigInteger> CFVideoList (BigInteger userid){ //召回算法
        List<BigInteger> videoList = new ArrayList<>();
        //1.召回
        videoList.addAll(ItemCF(userid));
        log.info("itemcf:{}",videoList);
        videoList.addAll(UserCF(userid));
        log.info("usercf:{}",videoList);
        videoList.addAll(getHotVideos());
        log.info("hotvideo:{}",videoList);
        videoList.addAll(getNewVideos());

        videoList.addAll(FollowUserUpVideo(userid));
        //2.去重
        videoList = orderList(videoList);
        //3.过滤不喜欢

        log.info("videoListFinally:{}",videoList.size());
        //4.过滤历史
        if(userid != null && !userid.equals(BigInteger.valueOf(-1))){

            videoList = filterHistory(videoList,userid);
        }

        log.info("recalledVideoIds:{}",videoList);
        return videoList;
    }

    private  List<BigInteger> FollowUserUpVideo(BigInteger userid) { //关注用户上传视频
        List<BigInteger> videoList =   new ArrayList<>();
        List<BigInteger> subUserList = relationService.getSubs(userid);
        for(BigInteger userId:subUserList){
          List<BigInteger> videoIds =   videoService.getUpVideoIds(userId);
          videoList.addAll(videoIds);
        }
        return videoList;
    }

    private List<BigInteger> filterHistory(List<BigInteger> videoList,BigInteger userid) {
        List<BigInteger> watchedVideoIds =   historyService.getAllWatchedVideoIds(userid);
        log.info("History:{}",watchedVideoIds.size());
        if (watchedVideoIds != null && !watchedVideoIds.isEmpty()) {

            HashSet hs1 = new HashSet(videoList);
            HashSet hs2 = new HashSet(watchedVideoIds);
            hs1.removeAll(hs2);
            List<BigInteger> listC = new ArrayList<>();
            listC.addAll(hs1);
            videoList =  listC;
          /*  log.debug("User {} has watched {} videos. Filtering them out.", userid, watchedVideoIds.size());
            int originalSize = videoList.size();
            // 使用 removeIf 高效过滤
            videoList.removeIf(watchedVideoIds::contains);
            log.info("Filtered out {} watched videos for user {}. Remaining: {}",
                    originalSize - videoList.size(), userid, videoList.size());*/
        } else {
            log.debug("User {} has no watch history to filter or service returned empty.", userid);
        }
        return videoList;
    }

    private List<BigInteger> getNewVideos() {
        int count = 5;
        return videoService.findRandomNewVideoIds(count);
    }

    private List<BigInteger> getHotVideos() { //获取全局热门视频;
        int count = 5;
        return videoService.findTopNHotVideoIds(count);
    }

    /**
     * 初步排序 (Coarse Ranking)
     * @param recalledVideoIds 从召回层获取的视频ID列表
     * @param userid 用户ID
     * @return 初步排序并截断后的视频ID列表
     */
    private List<BigInteger> SimpleOrderVideoList(List<BigInteger> recalledVideoIds, BigInteger userid) {
        if (recalledVideoIds == null || recalledVideoIds.isEmpty()) {
            return Collections.emptyList();
        }
        System.out.println("User " + userid + ": Starting Coarse Ranking with " + recalledVideoIds.size() + " candidates.");
        List<BigInteger> sortedVideoList = new ArrayList<>();
        Map<BigInteger,Double> scoreMap = new HashMap<>();
        for(BigInteger videoId:recalledVideoIds){ //获取进入粗排的视频id
            //获取分数
            // 视频热度 (Popularity): 可以是最近一段时间的播放量、点赞数、或点赞率等。这个数据可以离线计算好，存储在数据库或缓存中。
            //视频新鲜度 (Freshness): 视频的发布时间。越新的视频可能越有吸引力。
            //上传者热度 (Uploader Popularity): 上传者的粉丝数或近期视频的平均表现。也可以预计算。
            //召回分数 (Recall Score - 可选): 如果你的 UserCF/ItemCF 召回能提供一个相关性分数，也可以直接利用。
            //简单匹配度 (Basic Match - 可选): 比如，视频标签是否与用户最近点赞/观看达标的视频标签一致（因为你只有一个标签，这个作用可能有限，但可以作为思路）。
            //评分公式 (示例):
            //Score = w1 * log(近期播放量 + 1) + w2 * (1 / (当前时间 - 发布时间 + 1)) + w3 * log(上传者粉丝数 + 1) + w4 * 召回分数
            //w1, w2, w3, w4 是权重，需要根据经验初步设定，后续可以通过 A/B 测试或简单模型拟合来优化。
            //使用 log 或 1/x 是为了平滑数值，避免极端值影响过大。加 1 是为了避免 log(0) 或除以零。
            Video video= videoService.getVideo(videoId);
            if (video == null) {
                log.warn("Video info not found for videoId: {}, skipping.", videoId);
                continue; // 如果视频信息缺失，跳过该视频
            }
            double w1 = 0.5;
            double w2 = 0.3;
            double w3 = 0.2;
            double Popularity =Math.log(historyService.getViewCount(videoId)+1.0) ;

            User uploader =  userService.getById(video.getUserId());

            String time= video.getCreateTime();
            double Freshness = 0;
            if(time!=null){
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                LocalDateTime targetDateTime = LocalDateTime.parse(time, formatter);
                LocalDateTime currentDateTime = LocalDateTime.now();

                Duration duration = Duration.between(targetDateTime, currentDateTime);
                long days = duration.toDays();
                long hours = duration.toHours() % 24;
                long minutes = duration.toMinutes() % 60;
                long seconds = duration.getSeconds() % 60;
                Freshness  = 1.0 / (Math.max(days,0) + 1.0);
            }

            double Uploader_Popularity =Math.log(Math.max(uploader.getFans(),0)+1.0) ;
            double score = w1*Popularity+w2*Freshness+w3*Uploader_Popularity;
            scoreMap.put(videoId, score);
        }
        int topN = 100;
        sortedVideoList = this.videoSort(topN,scoreMap);
        return sortedVideoList;
    }

    private List<BigInteger> ComplexOrderVideoList (List<BigInteger> firstVideoList,BigInteger userid) { //精准排序

        List<VideoScore> videoScoreList = new ArrayList<>();
        List<VideoTag> videoTags = videoService.getTagIds(firstVideoList);
        for(VideoTag videoTag:videoTags){
            VideoScore videoScore = new VideoScore();
            videoScore.setVideoId(videoTag.getVideoId());
            videoScore.setUserId(userid);
            BigInteger tagId = videoTag.getTagId();
            if(tagId==null){
                tagId = BigInteger.valueOf(-1);
            }
            videoScore.setTagId(tagId);
            videoScoreList.add(videoScore);
        }
        Map<BigInteger,Double> scoreMap = predictService.predictVideoList(videoScoreList);
        int topN = 20;
        List<BigInteger> sortedVideoList = this.videoSort(topN,scoreMap);

        return sortedVideoList;

    }

    private List<BigInteger> ReorderVideoList (List<BigInteger> fineRankedVideoList,BigInteger userid){
   /*          需要从几百篇笔记中选出几十篇笔记，常见的方法有MMR和DPP抽样的时候有两个依据，
        一个依据是精排分数的大小，另一个依据是多样性。
        做完抽样之后，会用规则打散相似内容。
        我们不能把内容过于相似的笔记排在相邻的位置上。
        举个例子，根据金牌得到的分数，排前五的笔记全都是NBA的内容，这样就不太合适。
        即使用户是个篮球迷，他也未必希望看到同质化的内容。
        如果排第一的是NBA的笔记，那么接下来几个位置就不能放NBA的内容，相似的笔记会往后挪。
        重排的另一个目的是插入广告和运营，推广的内容还要根据生态的要求调整排序，比如不能连接出很多美女图片。*/

        if (fineRankedVideoList == null) {
            return Collections.emptyList();
        }
        return new ArrayList<>(fineRankedVideoList); // 返回副本，避免修改原列表
    }

}
